/*==========================================================*/
//
// OBJObject written by Thomas Kropf
//  http://www.thomaskropf.at
// based on  OBJ Loader written by Kamran (2218)
//
/*==========================================================*/

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <cstdlib> 
#include <ctime> 
#include <cmath> 
#include <list> 
#include <sstream> 

#include "common.h"
#include "vertex.h"
#include "objobject.h"
#include "triangle.h"
#include "texture.h"
#include "texturemanager.h"


int light=1;
int smooth=0;
float zaxis = -3.0f;

float g_posX, g_posY, g_posZ;


struct mousestruct {
	int oldx;
	int oldy;
	int x;
	int y;
	int leftbuttondown;
	int middlebuttondown;
	int rightbuttondown;
} mouse;

//change BOTH!
#define TIMERMSECS 40
#define TIMERFPS 25

#define ROTRATE 45.0f

int startTime;
int prevTime;

GLfloat rot = 0.0f;

long wWidth = 1024;
long wHeight = 768;

int drawFrameNumber = 0;


//definition of variable
//OBJObject *mainOBJObject;
//OBJObject *OBJObject2;

ObjectManager *myObjectManager;
//Texture *myTexture;
TextureManager *myTextureManager;

GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightPosition[]= { 0.5f, 1.0f, -4.0f, 1.0f };	

using namespace std;

float rotationvary = 0;
float rotationvarx = 0;

void gl_keypressFunc(unsigned char key, int x, int y){
			static int ibc=1;
			char s_ibc[10];
        switch(key) {
                case 27:
                        glutPostRedisplay();
                        exit(0);
                        break;
                case 32: //SPACE
			rotationvary+=5.0f;
			LOG("rotating " << rotationvary << " degrees..");
			//myObjectManager->getOBJObject("objfiles/fordfiesta.obj");
			myObjectManager->createOBJObject("objfiles/fordfiesta.obj", "ford001");
			glutPostRedisplay();
                        break;

                case 13: //ENTER
			//mainOBJObject->printVertexList();
			//mainOBJObject->printVertexNormalList();
			//mainOBJObject->printTriangleList();
			//myObjectManager->getOBJObject("objfiles/robotarmtex.obj");
			myObjectManager->createOBJObject("objfiles/robotarmtex.obj", "robotarmtex001");
                        glutPostRedisplay();
                        break;
                case 49: //1
			zaxis-=1;
			std::cout << "zaxis = " << zaxis << std::endl;
                        glutPostRedisplay();
                        break;
                case 50: //2
			zaxis+=1;
			std::cout << "zaxis = " << zaxis << std::endl;

			//just testing			
			OBJAnim *tempOBJAnimation;
			tempOBJAnimation = myObjectManager->createOBJAnim("objfiles/character_walking_#.obj", "CharWalkAnim");
			if (tempOBJAnimation!=NULL) tempOBJAnimation->setWorldCoords(-10.0f, 0.0f, -50.0f);
			if (tempOBJAnimation!=NULL) tempOBJAnimation->m_visible = 1;
			if (tempOBJAnimation!=NULL) tempOBJAnimation->m_fps = 10;

			tempOBJAnimation = myObjectManager->createOBJAnim("objfiles/character_walking_#.obj", "CharWalkAnim2");
			if (tempOBJAnimation!=NULL) tempOBJAnimation->setWorldCoords(10.0f, 0.0f, -50.0f);
			if (tempOBJAnimation!=NULL) tempOBJAnimation->m_visible = 1;
			if (tempOBJAnimation!=NULL) tempOBJAnimation->m_fps = 10;

//			printf("%04u\n", ibc); 
//			sprintf(s_ibc, "%06u", ibc);
//			std::cout << "s_ibc: " << s_ibc << std::endl;
//			ibc++;
                        glutPostRedisplay();
                        break;
			//start or stop animation
                case 51: //3
			OBJAnim *tempOBJAnimation2;
			tempOBJAnimation2 = myObjectManager->findAnimObject("CharWalkAnim");
			if (tempOBJAnimation2 != NULL) {
				if (tempOBJAnimation2->m_animationEnabled == 0)
					tempOBJAnimation2->enableAnim();
				else tempOBJAnimation2->disableAnim();
			}
                        glutPostRedisplay();
                        break;
			//start animation
                case 52: //4
			OBJAnim *tempOBJAnimation3;
			tempOBJAnimation3 = myObjectManager->findAnimObject("CharWalkAnim2");
			if (tempOBJAnimation3 != NULL) tempOBJAnimation3->enableAnim();
			else std::cout << "error enabling animation\n";
                        glutPostRedisplay();
                        break;
		case 108: //l
			if (light == 1)				// If Not Light
			{
				glDisable(GL_LIGHTING);		// Disable Lighting
				light = 0;
				glutPostRedisplay();
			}
			else					// Otherwise
			{
				glEnable(GL_LIGHTING);		// Enable Lighting
				light = 1;
				glutPostRedisplay();
			}
		case 115: //s
			if (smooth == 1)
			{
				glShadeModel(GL_FLAT);
				smooth = 0;
			}
			else if (smooth == 0) {
				glShadeModel(GL_SMOOTH);
				smooth = 1;
			}
			glutPostRedisplay();
        }
}
void gl_MouseProcess(int button, int state, int x, int y) {
	if (state == GLUT_DOWN) {
		if (button == GLUT_LEFT_BUTTON) {
			mouse.leftbuttondown = 1;
			mouse.oldx = x;
			mouse.oldy = y;
		}
		if (button == GLUT_MIDDLE_BUTTON) {
			mouse.middlebuttondown = 1;
			mouse.oldx = x;
			mouse.oldy = y;
		}
		if (button == GLUT_RIGHT_BUTTON) {
			GLint viewport[4];
			GLdouble modelview[16];
			GLdouble projection[16];
			GLfloat winX, winY, winZ;
			GLdouble posX, posY, posZ;
	
			glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
			glGetDoublev( GL_PROJECTION_MATRIX, projection );
			glGetIntegerv( GL_VIEWPORT, viewport );

			winX = (float)x;
			winY = (float)viewport[3] - (float)y;
			glReadPixels( x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );

			gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
			std::cout << "click: 3D coordinates: " << posX << ", " << posY << ", " << posZ << std::endl;
			g_posX = posX; g_posY = posY; g_posZ = posZ;
	

			std::list <OBJObject*>::iterator t_objIt;
			for (t_objIt=(myObjectManager->m_objobjectList).begin(); 
			t_objIt != (myObjectManager->m_objobjectList).end(); t_objIt++) {
				if ((*t_objIt)->testBBIntersection(posX, posY, posZ) == 1) {
//					(*t_objIt)->m_selected = 1;
					(*t_objIt)->toggleSelected();
				}
			}
		}
	}
	else if (state == GLUT_UP) {
		if (button == GLUT_LEFT_BUTTON) {
			mouse.leftbuttondown = 0;
		}
		if (button == GLUT_MIDDLE_BUTTON) {
			mouse.middlebuttondown = 0;
		}
		if (button == GLUT_RIGHT_BUTTON) {
			mouse.rightbuttondown = 0;
		}
	}
}

void gl_MousePassiveMotion(int x, int y) {
			GLint viewport[4];
			GLdouble modelview[16];
			GLdouble projection[16];
			GLfloat winX, winY, winZ;
			GLdouble posX, posY, posZ;
	
			glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
			glGetDoublev( GL_PROJECTION_MATRIX, projection );
			glGetIntegerv( GL_VIEWPORT, viewport );

			winX = (float)x;
			winY = (float)viewport[3] - (float)y;
			glReadPixels( x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );

			gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
			std::cout << "click: 3D coordinates: " << posX << ", " << posY << ", " << posZ << std::endl;
			g_posX = posX; g_posY = posY; g_posZ = posZ;
}

void gl_MouseActiveMotion(int x, int y) {
	if (mouse.leftbuttondown == 1) {
		rotationvary -= mouse.oldx - x;
		rotationvarx += mouse.oldy - y;
//		std::cout << "x difference = " << mouse.oldx - x << "\n";
		mouse.oldx = x;
		mouse.oldy = y;
//		std::cout << "mouse moved: " << x << " " << y << "\n";
		glutPostRedisplay();
	}
	if (mouse.middlebuttondown == 1) {
		if ((zaxis+(mouse.oldy - y)) <= -3.0f) {
			zaxis += (mouse.oldy - y);
		//	std::cout << "zaxis = " << zaxis << std::endl;
			mouse.oldy = y;	
			glutPostRedisplay();
		}
		else if ((zaxis+(mouse.oldy - y)) > -3.0f) {
			zaxis = -3.0f;
			glutPostRedisplay();
		}
	}
}

/**
* \brief timer function
*/
void gl_animate(int value) {
	// Set up the next timer tick (do this first)
	glutTimerFunc(TIMERMSECS, gl_animate, 0);

	// Measure the elapsed time
	int currTime = glutGet(GLUT_ELAPSED_TIME);
	int timeSincePrevFrame = currTime - prevTime;
	int elapsedTime = currTime - startTime;

	// Rotate the triangle
	rot = (ROTRATE / 1000) * elapsedTime;

//	std::cout << "timeSincePrevFrame: " << timeSincePrevFrame << std::endl;

//	if (drawFrameNumber <= 10 ) {
//		drawFrameNumber++;
//	}
//	if (drawFrameNumber == 11) drawFrameNumber = 1;
	//max frame number
	drawFrameNumber = (drawFrameNumber % TIMERFPS) + 1;

//	std::cout << "draw frame number: " << drawFrameNumber << std::endl;
//	std::cout << "draw frame number / 2.5: " << ceil(drawFrameNumber/2.5) << std::endl;


	// Force a redisplay to render the new image
	glutPostRedisplay();

	prevTime = currTime;
}

void display(void)
{    
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective( 60.0, (GLfloat)wWidth/(GLfloat)wHeight, 1.0, 500.0 );

	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();

	 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_LIGHT0);
	glLoadIdentity();
    	glTranslatef(0.0, 0.0, zaxis);
	glRotatef(rotationvary, 0, 1, 0);
	glRotatef(-rotationvarx, 1, 0, 0);

	//frame-independent rotation
//	glRotatef(rot, 0, 1, 0);
    
	char line[100];
	FILE *fp;    


	//draw ALL OBJobjects	

	std::list <OBJObject*>::iterator objIt;
	for (objIt=(myObjectManager->m_objobjectList).begin(); 
		objIt != (myObjectManager->m_objobjectList).end(); objIt++) {

		//textures on or off?
		if (((*objIt)->m_texturefilename).size()>0) {
			glBindTexture(GL_TEXTURE_2D, 
			 (myTextureManager->getTexture((*objIt)->m_texturefilename))->getGLTexID());
		}
		else {
			glBindTexture(GL_TEXTURE_2D, 0);
		}

		(*objIt)->drawObject();

		if ((*objIt)->m_selected == 1) {
			(*objIt)->drawBoundingBox();
		}
	}


	/// TODO \todo Textures for animations not "enabled" yet DONE BUT NEEDS TESTING
	//draw ALL Animation Objects objAnim
	std::list <OBJAnim*>::iterator objAnimIt;
	//cycle through all objAnim objects
	for (objAnimIt=(myObjectManager->m_objAnimList).begin();
		objAnimIt != (myObjectManager->m_objAnimList).end(); objAnimIt++) {

		//textures on or off?		
		if (((*objAnimIt)->m_texturefilename).size()>0) {
			glBindTexture(GL_TEXTURE_2D, 
			 (myTextureManager->getTexture((*objAnimIt)->m_texturefilename))->getGLTexID());
		}
		else {
			glBindTexture(GL_TEXTURE_2D, 0);
		}


		if ((*objAnimIt)->m_visible == 1) {
//		if ((*objAnimIt)->m_visible == 1 && (*objAnimIt)->m_animationEnabled == 1) {

			glPushMatrix();

			//use world coordinates
			glTranslatef((*objAnimIt)->m_worldX, (*objAnimIt)->m_worldY, (*objAnimIt)->m_worldZ); 
	//		 std::cout << "iterating over objAnim objects..." << std::endl;
	//		 std::cout << "objAnim m_numFrames: " << (*objAnimIt)->m_numFrames << std::endl;
			OBJObject *tempOBJ;

			/// TODO \todo add offset (and use modulo operation) so that stopped
			//animation continues with the same frame
			if ((*objAnimIt)->m_animationEnabled == 1) {
				int calcFrame = ceil((float)drawFrameNumber / (float)(TIMERFPS / ((*objAnimIt)->m_fps)) );
				tempOBJ = (*objAnimIt)->getFrame(calcFrame);
			}
			//if animation is stopped than get last frame
			else {
				tempOBJ = (*objAnimIt)->getFrame( (*objAnimIt)->m_lastFrame );
			}
//			std::cout << "frame: " << ceil(drawFrameNumber / (float)(TIMERFPS / ((*objAnimIt)->m_fps)) ) << std::endl;
			if (tempOBJ==NULL) {
				std::cout << "Couldn't find frame" << std::endl;
				break;
			}
	//		 std::cout << "tempOBJ = " << tempOBJ << std::endl;
	//		 std::cout << "tempOBJ m_uniqName = " << tempOBJ->m_uniqName << std::endl;
			if (tempOBJ != NULL) tempOBJ->drawObject();

			glPopMatrix();
		}
	}
	
	//draw marker at light location
	glBegin(GL_LINES);
		glColor3f(1.0, 1.0, 1.0);
		glVertex3f(LightPosition[0]-2.0f, LightPosition[1], LightPosition[2]);
		glVertex3f(LightPosition[0]+2.0f, LightPosition[1], LightPosition[2]);
		
		glVertex3f(LightPosition[0], LightPosition[1]-2.0f, LightPosition[2]);
		glVertex3f(LightPosition[0], LightPosition[1]+2.0f, LightPosition[2]);
		
		glVertex3f(LightPosition[0], LightPosition[1], LightPosition[2]-2.0f);
		glVertex3f(LightPosition[0], LightPosition[1], LightPosition[2]+2.0f);
	glEnd();

	//draw marker at click location
	glBegin(GL_LINES);
		glColor3f(1.0, 1.0, 1.0);
		glVertex3f(g_posX - 2.0f, g_posY, g_posZ);
		glVertex3f(g_posX + 2.0f, g_posY, g_posZ);
			
		glVertex3f(g_posX, g_posY - 2.0f, g_posZ);
		glVertex3f(g_posX, g_posY + 2.0f, g_posZ);
				
		glVertex3f(g_posX, g_posY, g_posZ - 2.0f);
		glVertex3f(g_posX, g_posY, g_posZ + 2.0f);
			
	glEnd();

/*
	//draws one specific OBJObject
	//temporary variables added to simplify texture binding
	OBJObject *tempOBJ = myObjectManager->findObject("objfiles/simplehouseComplete.obj");
	Texture *tempTex = myTextureManager->getTexture(tempOBJ->m_texturefilename);
	glBindTexture(GL_TEXTURE_2D, tempTex->getGLTexID());
	myObjectManager->drawObject("objfiles/simplehouseComplete.obj");
*/

	//glBindTexture(GL_TEXTURE_2D, 
	//	(myTextureManager->getTexture((myObjectManager->findObject("objfiles/simplehouseComplete.obj"))->m_texturefilename))->getGLTexID());
	// Don't wait start processing buffered OpenGL routines
	glFlush();
	glutSwapBuffers();
}

void gl_reshapeFunc(int width, int height)
{
	wWidth=width;
	wHeight=height;
	std::cout << "wWidth and wHeight: " << wWidth << " " << wHeight << std::endl;
//    if (height==0)                              // Prevent A Divide By Zero If The Window Is Too Smal
//	height=1;

	glViewport(0, 0, wWidth, wHeight);            // Reset The Current Viewport And Perspective Transfo
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0f,(GLfloat)wWidth/(GLfloat)wHeight,0.1f,100.0f);
//	glMatrixMode(GL_MODELVIEW);
}

void init(void)
{
  
    //Setup Display
//    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitDisplayMode ( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(1024,768);
    glutInitWindowPosition(100,100);
    glutCreateWindow("OBJObject written by Thomas Kropf");
   
	glEnable(GL_DEPTH_TEST);

	//LIGHTING
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);	
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);


	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_NORMALIZE);
	glShadeModel(GL_FLAT);


    //select clearing (background) color
    glClearColor(0.0, 0.0, 0.0, 0.0);

}

int main(int argc, char *argv[])
{
    //Initialise GLUT with command-line parameters. 
    glutInit(&argc, argv);
    
    //Call init (initialise GLUT)
    init();
    
    //Call "display" function
	glutDisplayFunc(display);
	glutKeyboardFunc(gl_keypressFunc);
	glutMotionFunc(gl_MouseActiveMotion);
	glutMouseFunc(gl_MouseProcess);
//	glutPassiveMotionFunc(gl_MousePassiveMotion);
	glutReshapeFunc(gl_reshapeFunc);

	//start the timer
	glutTimerFunc(TIMERMSECS, gl_animate, 0);

	startTime = glutGet(GLUT_ELAPSED_TIME);
	prevTime = startTime;


	srand((unsigned)time(0)); 
//	mainOBJObject = new OBJObject;
	myObjectManager = new ObjectManager;
	myTextureManager = new TextureManager;
	if (argc == 2) {
		//myObjectManager->getOBJObject(argv[1]);
		myObjectManager->createOBJObject(argv[1], argv[1]);
//		mainOBJObject->analyzeFile(argv[1]);
//		mainOBJObject->loadFile(argv[1]);
//		if (mainOBJObject->m_texturefilename.size()>0) {
			//LoadGLTextures(mainOBJObject->m_texturefilename);
			//myTexture->loadGLTextures(mainOBJObject->m_texturefilename);
//			myTextureManager->getTexture(mainOBJObject->m_texturefilename);
//			glEnable(GL_TEXTURE_2D); 
//		}
	}
	else if (argc == 1 ) {
//		OBJObject2 = new OBJObject;
	//	std::cout << "trying to load file" << "\n";
//		mainOBJObject->analyzeFile("objfiles/robotarmtex.obj");
//		mainOBJObject->loadFile("objfiles/robotarmtex.obj");
//		OBJObject2->analyzeFile("objfiles/simplehouseComplete.obj");
//		OBJObject2->loadFile("objfiles/simplehouseComplete.obj");
//		if (mainOBJObject->m_texturefilename.size()>0) {
//			myTextureManager->getTexture(mainOBJObject->m_texturefilename);
//			glEnable(GL_TEXTURE_2D); 
//		}
//		if (OBJObject2->m_texturefilename.size()>0) {
//			myTextureManager->getTexture(OBJObject2->m_texturefilename);
//			glEnable(GL_TEXTURE_2D); 
//		}
	} 
    //Enter the GLUT event loop
    glutMainLoop();

    return 0;
}

